{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# iSWAP Gate\n",
    "\n",
    "*Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Outline\n",
    "This tutorial will demonstrate how to generate the fideliy-optimized pulse for the iSWAP gate using Quanlse. The outline of this tutorial is as follows:\n",
    "- Introduction\n",
    "- Preparation\n",
    "- Quanlse optimization\n",
    "- Summary\n",
    "\n",
    "In the **Introduction** section, we will give a brief introduction on iSWAP gate and construct the system Hamiltonian. Then we will demonstrate how to generate fideliy-optimized pulse sequence using Quanlse Cloud Service in the **Quanlse optimization** section."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Introduction\n",
    "The iSWAP gate swaps the excitations between two qubits and adds a phase of $i$ \\[1\\].\n",
    "The corresponding unitary matrix is:\n",
    "$$\n",
    "\\rm iSWAP = \\begin{pmatrix}\n",
    "    1 & 0 & 0 & 0 \\\\\n",
    "    0 & 0 & i & 0 \\\\\n",
    "    0 & i & 0 & 0 \\\\\n",
    "    0 & 0 & 0 & 1   \n",
    "\\end{pmatrix}. \\\\ \n",
    "$$\n",
    "The iSWAP gate is a native gate in superconducting quantum computing platforms, because it can be realized directly using coupling between two superconducting qubits (XY interaction). In superconducting circuits, the iSWAP gate is implemented by tuning the qubits into resonance in a duration of time. By performing a rotating wave approximation, the two-qubit coupling Hamiltonian can be written as \\[2\\]:\n",
    "$$\n",
    "\\hat H_{\\rm couple} = g(e^{i\\delta_{12}t}\\hat{\\sigma}_1^+\\hat{\\sigma}_2^-+e^{-i\\delta_{12}t}\\hat{\\sigma}_1^-\\hat{\\sigma}_2^+),\n",
    "$$\n",
    "where $\\delta_{12}=\\omega_{q1}-\\omega_{q2}$ is the detuning. By tuning the frequency of qubit 1 into resonance with qubit 2, the detuning $\\delta_{12}$ reduces to $0$; The truncated operators $\\sigma_i^+$ and $\\sigma_i^-$ are creation operator and annihilation operator of qubit $i$. The coupling Hamiltonian reduces to:\n",
    "$$\n",
    "\\hat H_{\\rm couple} = g(\\hat{\\sigma}_1^+\\hat{\\sigma}_2^-+\\hat{\\sigma}_1^-\\hat{\\sigma}_2^+),\n",
    "$$\n",
    "which swaps the excitations of the two qubits.\n",
    "\n",
    "Moreover, two iSWAP gates and several single-qubit gates can be used to generate a CNOT gate. The generation of GHZ states utilizing the iSWAP gates has also been demonstrated \\[2\\]."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preparation\n",
    "After you have successfully installed Quanlse, you could run the Quanlse program below following this tutorial. To run this particular tutorial, you would need to import the following packages from Quanlse and other commonly-used Python libraries:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import numpy and math constants\n",
    "from numpy import round\n",
    "from math import pi\n",
    "\n",
    "# Import the Hamiltonian module\n",
    "from Quanlse.Utils import Hamiltonian as qham\n",
    "\n",
    "# Import related packages\n",
    "from Quanlse.Utils.Operator import duff, number\n",
    "from Quanlse.remoteOptimizer import remoteOptimizeISWAP\n",
    "\n",
    "# Import tools for analysing results\n",
    "from Quanlse.Utils.Tools import project"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To use the Quanlse Cloud Service, we need to acquire a token to get access to the cloud."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import Define class and set the token for cloud service\n",
    "# Please visit http://quantum-hub.baidu.com\n",
    "from Quanlse import Define\n",
    "Define.hubToken = \"\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Quanlse optimization"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To perform optimization using Quanlse, we need to specify the system Hamiltonian. We consider the three lowest energy levels of each qubit where the third level accounts for leakage. In the rotating frame, the system Hamiltonian of two coupled qubits is expressed as \\[1\\]:\n",
    "$$\n",
    "\\hat H =(\\omega_{q1}-\\omega_{d1})(\\hat a^\\dagger_1 \\hat a_1)+(\\omega_{q2}-\\omega_{d2})(\\hat a^\\dagger_2 \\hat a_2)+ \\frac{\\alpha _1}{2}\\hat a^\\dagger_1 \\hat a^\\dagger_1 \\hat a_1 \\hat a_1 + \\frac{\\alpha_2}{2}\\hat a^\\dagger_2 \\hat a^\\dagger_2 \\hat a_2 \\hat a_2 +\\frac{g}{2}(\\hat a_1\\hat a_2^\\dagger+\\hat a_1^\\dagger \\hat a_2)+\\frac{A^z_1(t)}{2}\\hat a^\\dagger_1 \\hat a_1,\n",
    "$$\n",
    "where $\\hat a_i^\\dagger$ ($\\hat a_i$) is the creation (annihilation) operator of qubit $i$ ($i = 1,2$); $\\omega _{qi}$ is the frequency of qubit $i$; $\\alpha_i$ is the anharmonicity of qubit $i$; and $g$ is the coupling strength between the qubits. $A^z_1(t)$ is the waveform function that controls the flux of qubit 1 and tunes its frequency."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Quanlse defines the system Hamiltonian by specifying the sampling period, gate time and everything else necessary to set up the calculation. Quanlse provides the flexibility to customize simulation for practically any gate operations, particularly those involving parameter optimization. First, we define a couple of parameters to set the sampling period, the number of qubits, and the system dimension: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Sampling period\n",
    "dt = 1.0\n",
    "# Number of qubits\n",
    "qubits = 2\n",
    "# System energy level\n",
    "level = 3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We then define the transition frequencies, drive frequencies, and anharmonicities of the two qubits and the coupling strength in between:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "qubitArgs = {}\n",
    "# Coupling strength, GHz\n",
    "g = 0.0277 * (2 * pi)\n",
    "# Transition frequency, GHz\n",
    "qubit_freq0 = 5.805 * (2 * pi)\n",
    "qubit_freq1 = 5.205 * (2 * pi)\n",
    "# Microwave drive frequency, GHz\n",
    "drive_freq0 = qubit_freq1\n",
    "drive_freq1 = qubit_freq1\n",
    "# Anharmonicity, GHz\n",
    "anharm0 = -0.217 * (2 * pi)\n",
    "anharm1 = -0.226 * (2 * pi)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then, we can proceed to create an empty Hamiltonian dictionary using the `createHam()` function and add terms to it based on previously defined parameters:\n",
    "$$\n",
    "\\hat H_{\\rm couple} = \\frac{g}{2}(\\hat a_1\\hat a_2^\\dagger+\\hat a_1^\\dagger \\hat a_2),\n",
    "$$\n",
    "$$\n",
    "\\hat H_{\\rm ctrl} = \\frac{A^z_1(t)}{2}\\hat a^\\dagger_1 \\hat a_1,\n",
    "$$\n",
    "$$\n",
    "\\hat H_{\\rm drift} = (\\omega_{q1}-\\omega_{d1})(\\hat a^\\dagger_1 \\hat a_1)+(\\omega_{q2}-\\omega_{d2})(\\hat a^\\dagger_2 \\hat a_2)+\\frac{\\alpha _1}{2}\\hat a^\\dagger_1 \\hat a^\\dagger_1 \\hat a_1 \\hat a_1 + \\frac{\\alpha_2}{2}\\hat a^\\dagger_2 \\hat a^\\dagger_2 \\hat a_2 \\hat a_2.\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ham = qham.createHam(title=\"2q-3l\", dt=dt, qubitNum=qubits, sysLevel=level)\n",
    "\n",
    "# Add the detuning terms\n",
    "detune0 = qubit_freq0 - drive_freq0\n",
    "qham.addDrift(ham, name=f\"q0-detuning\", onQubits=0, matrices=number(level), amp=detune0)\n",
    "detune1 = qubit_freq1 - drive_freq1\n",
    "qham.addDrift(ham, name=f\"q1-detuning\", onQubits=1, matrices=number(level), amp=detune1)\n",
    "# Add the anharmonicity terms\n",
    "qham.addDrift(ham, name=f\"q0-anharm\", onQubits=0, matrices=duff(level), amp=0.5 * anharm0)\n",
    "qham.addDrift(ham, name=f\"q1-anharm\", onQubits=1, matrices=duff(level), amp=0.5 * anharm1)\n",
    "# Add the coupling term\n",
    "qham.addCoupling(ham, name=\"coupling\", onQubits=[0, 1], g=g / 2.0)\n",
    "# Add the control term\n",
    "qham.addControl(ham, name=\"q0-ctrlz\", onQubits=0, matrices=number(level))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "All useful information regarding the system Hamiltonian is stored in `ham`. We should set a boundary for amplitudes before launching the optimization. You might need to run the optimization several times and narrow down the boundary gradually."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "aBound = (-5, 5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we are ready to run the optimization. The `remoteOptimizeISWAP()` function takes five arguments. `tg` specifies the gate time. When `targetInfidelity` is reached, or the number of iterations exceeds `maxIter`, the optimization will terminate and return the minimum infidelity and the system Hamiltonian with optimized control terms. We can plot the pulses by calling `qham.plotWaves()` and get the unitary evolution by calling `qham.getUnitary()`. \n",
    "\n",
    "The gate infidelity for performance assessment throughout this tutorial is defined as ${\\rm infid} = 1 - \\frac{1}{d}\\left|{\\rm Tr}\\left[U^\\dagger_{\\rm goal}P(U)\\right]\\right|$, where $d$ is the dimension of $U_{\\rm goal}$ $(U_{\\rm goal} = {\\rm iSWAP})$ and $U$ is the unitary evolution of the three-level system. Note that $P(U)$ in particular describes the evolution projected to the computational subspace. We can first  run the optimization then calculate the projected evolution $P(U)$ :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# Run the optimization\n",
    "ham, infidelity = remoteOptimizeISWAP(ham, aBound=aBound, tg=40, maxIter=3, targetInfidelity=0.005)\n",
    "\n",
    "# Print infidelity and the waveforms\n",
    "print(f\"minimum infidelity: {infidelity}\")\n",
    "qham.plotWaves(ham, [\"q0-ctrlz\"])\n",
    "\n",
    "# Print the projected evolution\n",
    "result = qham.simulate(ham)\n",
    "print(\"The projected evolution P(U):\\n\", round(project(result[\"unitary\"], qubits, level, 2), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can see that the drive strength of the pulse is around 3.8. We can zoom in and search for a better fidelity by narrowing down the boundary to (-4.0, -3.6)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "aBound = (-4.0, -3.6)\n",
    "# Run the optimization\n",
    "ham, infidelity = remoteOptimizeISWAP(ham, aBound, tg=40, maxIter=3, targetInfidelity=0.01)\n",
    "\n",
    "# Print infidelity and the waveforms\n",
    "print(f\"minimum infidelity: {infidelity}\")\n",
    "qham.plotWaves(ham, [\"q0-ctrlz\"])\n",
    "\n",
    "# Print the projected evolution \n",
    "result = qham.simulate(ham)\n",
    "print(\"The projected evolution P(U):\\n\", round(project(result[\"unitary\"], qubits, level, 2), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you wish to further reduce the infidelity, try setting the parameter `maxIter` to a larger number. With a set of reasonable parameters, Quanlse can find the optimal waveforms."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Summary\n",
    "This tutorial introduces the pulse optimization of the iSWAP gate using Quanlse. After reading this tutorial, the users are encouraged to try parameter values different from this tutorial to obtain the optimal result."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## References\n",
    "\\[1\\] [Schuch, Norbert, and Jens Siewert. \"Natural two-qubit gate for quantum computation using the XY interaction.\" *Physical Review A* 67.3 (2003): 032301.](https://link.aps.org/doi/10.1103/PhysRevA.67.032301)\n",
    "\n",
    "\\[2\\] [Krantz, Philip, et al. \"A quantum engineer's guide to superconducting qubits.\" *Applied Physics Reviews* 6.2 (2019): 021318.](https://doi.org/10.1063/1.5089550)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
